/**
 * @projectName: api-backend
 * @package: com.wangzz.apigateway
 * @className: GlobalFilter
 * @author: wangzz
 * @description: TODO
 * @date: 2024/3/15 9:23
 * @version: 1.0
 */

package com.wangzz.apigateway;

import cn.hutool.core.text.CharSequenceUtil;
import com.wangzz.apiclientsdk.utils.SignUtil;
import com.wangzz.apicommon.common.ErrorCode;
import com.wangzz.apicommon.entity.InterfaceInfo;
import com.wangzz.apicommon.entity.User;
import com.wangzz.apicommon.exception.BusinessException;
import com.wangzz.apicommon.service.InnerApiService;
import com.wangzz.apicommon.service.InnerUserApiService;
import com.wangzz.apicommon.service.InnerUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.Host;
import org.apache.dubbo.config.annotation.DubboReference;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.channels.Pipe;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @Author: wangzz
 * @Description: 全局拦截器
 * @DateTime: 2024/3/15 9:23
 **/
@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @DubboReference
    private InnerUserService innerUserService;

    @DubboReference
    private InnerApiService innerApiService;

    @DubboReference
    private InnerUserApiService innerUserApiService;

    // 白名单地址
    private static final List<String> IP_WHITE_LIST = Arrays.asList("127.0.0.1");

    public static final String INTERFACE_HOST = "http://localhost:8123";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

//            2. 请求日志
        ServerHttpRequest request = exchange.getRequest();
        // 真实业务获取到
        String url = INTERFACE_HOST + request.getPath().value();
        String method = request.getMethod().toString();
        log.info("请求id：{}", request.getId());
        log.info("请求方法：{}", method);
        log.info("请求参数：{}", request.getQueryParams());
        log.info("请求路径：{}", url);
        String sourceAddress = request.getLocalAddress().getHostString();
        log.info("请求来源地址：{}", sourceAddress);
        log.info("请求来源地址：{}", request.getRemoteAddress());
        ServerHttpResponse response = exchange.getResponse();
//            3. 黑白名单
        if (!IP_WHITE_LIST.contains(sourceAddress)) {
            return handleAuthError(response);
        }
//            4. 用户鉴权（判断ak，sk是否合法）
        HttpHeaders headers = request.getHeaders();
        String accessKey = headers.getFirst("accessKey");
        String nonce = headers.getFirst("nonce");
        String timeStamp = headers.getFirst("timeStamp");
        String sign = headers.getFirst("sign");
        String body = null;
//        if ("POST".equals(method)) {
        try {
            body = URLDecoder.decode(headers.getFirst("body"), StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            log.error("解析body错误：{}", e.getMessage());
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "body解析失败");
        }
//        }
        // 从数据库中取出校验（远程调用）
        User user = null;
        try {
            user = innerUserService.getInvokeUser(accessKey);
        } catch (Exception e) {
            log.error("getInvokeUser error :", e);
        }
        // 用户不存在
        if (user == null) {
            return handleAuthError(response);
        }
        if (accessKey == null || !accessKey.equals(user.getAccessKey())) {
            return handleAuthError(response);
        }

        if (Long.parseLong(nonce) > 100000L) {
            return handleAuthError(response);
        }

        // 五分钟内有效
        Long currentTime = System.currentTimeMillis() / 1000;
        Long FIVE_MINUTES = 60 * 5L;
        if ((currentTime - Long.parseLong(timeStamp)) > FIVE_MINUTES) {
            return handleAuthError(response);
        }
        // 真实业务这里需要编写后端的签名和前端传来的作比较
        String secretKey = user.getSecretKey();
        String serverSign = SignUtil.getSign(body, secretKey);
        if (!CharSequenceUtil.equals(sign, serverSign)) {  // 两个都为null 为true，只有一个为null就为false
            return handleAuthError(response);
        }
//            5. 请求的模拟接口是否存在
        // 从数据库中查询模拟接口是否存在，校验url和请求方法也可以加上请求参数
        InterfaceInfo invokeInterface = null;
        try {
            invokeInterface = innerApiService.getInvokeInterface(url, method);
        } catch (Exception e) {
            log.error("getInvokeInterface error :", e);
        }
        if (invokeInterface == null) {
            return handleAuthError(response);
        }
        // 是否还有调用次数，调用成功接口次数+1，乐观锁解决接口调用并发
        boolean hasLeftNum = false;
        try {
            hasLeftNum = innerUserApiService.invokeCount(user.getId(), invokeInterface.getId());
        } catch (Exception e) {
            log.error("invokeInterfaceLeftNum error :", e);
            return handleInvokeError(response);
        }
        // 没有调用次数，或者接口不存在
        if (!hasLeftNum) {
            return handleAuthError(response);
        }
//            6. 请求转发，调用模拟接口
//        Mono<Void> filter = chain.filter(exchange);
////            7. 响应日志
//        log.info("响应状态：{}", response.getStatusCode());
////            8. 调用成功，接口调用次数+1
//        if (response.getStatusCode() == HttpStatus.OK) {
//
//        } else {
////            9. 调用失败，返回一个规范的错误码
//            return handleInvokeError(response);
//        }
        // 包含 6、7、8、9 这个方法解决异步，因为要执行完所有的filter，才能将响应结果返回，所以在执行的过程中，下面的得不到响应结果
        return handleResponse(exchange, chain, invokeInterface.getId(), user.getId());
    }

    /**
     * 处理响应 记录日志调用第三方接口
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> handleResponse(ServerWebExchange exchange, GatewayFilterChain chain, long interfaceId, long userId) {
        try {
            // 从交换机拿到原始response
            ServerHttpResponse originalResponse = exchange.getResponse();
            // 缓冲区工厂 拿到缓存数据
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            // 拿到状态码
            HttpStatus statusCode = originalResponse.getStatusCode();

            if (statusCode == HttpStatus.OK) {
                // 装饰，增强能力
                ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                    // 等调用完转发的接口后才会执行
                    @Override
                    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                        log.info("body instanceof Flux: {}", (body instanceof Flux));
                        // 对象是响应式的
                        if (body instanceof Flux) {
                            // 我们拿到真正的body
                            Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                            // 往返回值里面写数据
                            // 拼接字符串
                            return super.writeWith(fluxBody.map(dataBuffer -> {
                                // data从这个content中读取
                                byte[] content = new byte[dataBuffer.readableByteCount()];
                                dataBuffer.read(content);
                                DataBufferUtils.release(dataBuffer);// 释放掉内存
                                // 6.构建日志
                                List<Object> rspArgs = new ArrayList<>();
                                rspArgs.add(originalResponse.getStatusCode());
                                String data = new String(content, StandardCharsets.UTF_8);// data
                                rspArgs.add(data);
                                log.info("<--- status:{} data:{}"// data
                                        , rspArgs.toArray()); // log.info("<-- {} {}", originalResponse.getStatusCode(), data);
                                return bufferFactory.wrap(content);
                            }));
                        } else {
                            // 8.调用失败返回错误状态码
                            log.error("<--- {} 响应code异常", getStatusCode());
                        }
                        return super.writeWith(body);
                    }
                };
                // 设置 response 对象为装饰过的
                return chain.filter(exchange.mutate().response(decoratedResponse).build());
            }
            return chain.filter(exchange);// 降级处理返回数据
        } catch (Exception e) {
            log.error("gateway log exception.\n" + e);
            return chain.filter(exchange);
        }
    }

    private static Mono<Void> handleAuthError(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.FORBIDDEN);
        return response.setComplete();
    }

    private static Mono<Void> handleInvokeError(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        return response.setComplete();
    }

    @Override
    public int getOrder() {
        return -1;
    }
}